package com.googlecode.vmock;

import com.googlecode.vmock.matchers.Matcher;
import com.googlecode.vmock.utils.Validate;

/**
 * An expected call result that knows how many times it should be returned.
 * 
 * @author marcin.jagus
 */
abstract class AbstractExpectedCallResult implements ExpectedCallResult {

    /**
     * A call recorded in the {@link MockStage#RECORDING} stage.
     */
    private RecordedCall recordedCall;

    /**
     * How many times a result should be expected.
     */
    private int timesExpected;

    /**
     * How many times a result was returned.
     */
    private int timesInvoked;

    /**
     * Creates a new expected result for a given recorded call.
     * 
     * @param recordedCall A call recorded in the {@link MockStage#RECORDING} stage.
     */
    protected AbstractExpectedCallResult(RecordedCall recordedCall) {
        Validate.notNull(recordedCall, "recordedCall == null");

        this.recordedCall = recordedCall;
        this.timesExpected = MockBuilder.ANY_TIMES;
        this.timesInvoked = 0;
    }

    @Override
    public int getTimesExpected() {
        return timesExpected;
    }

    @Override
    public void setTimesExpected(int count) {
        if (count != MockBuilder.ANY_TIMES && count < 0) {
            throw new IllegalArgumentException("timesExpected < 0");
        }

        this.timesExpected = count;
    }

    @Override
    public int getTimesInvoked() {
        return timesInvoked;
    }

    /**
     * Increment the number of times this result has been invoked. Called only by subclasses.
     */
    protected void incrementTimesInvoked() {
        timesInvoked++;
    }

    @Override
    public RecordedCall getRecordedCall() {
        return recordedCall;
    }

    @Override
    public boolean matches(Object[] params) {
        Validate.notNull(params, "params == null");

        Matcher[] matchers = recordedCall.getMatchers();

        if (matchers.length != params.length) {
            throw new IllegalStateException("The number of parameters does not match the number of matchers defined for call " + recordedCall
                    + ". matchers: " + matchers.length + ", params: " + params.length);
        }

        for (int i = 0; i < matchers.length; i++) {
            if (!matchers[i].matches(params[i])) {
                return false;
            }
        }

        return true;
    }

    @Override
    public int compareTo(ExpectedCallResult o) {
        Validate.notNull(o, "o == null");

        if (!this.getRecordedCall().equals(o.getRecordedCall())) {
            throw new IllegalStateException("Cannot compare expected results for different recorded calls. " + this.getRecordedCall() + " != "
                    + o.getRecordedCall());
        }

        Matcher[] myMatchers = this.getRecordedCall().getMatchers();
        Matcher[] theirMatchers = o.getRecordedCall().getMatchers();

        if (myMatchers.length != theirMatchers.length) {
            throw new IllegalStateException("Numbers of matchers do not match for the same call.");
        }

        int comparsionResult = 0;

        for (int i = 0; i < myMatchers.length; i++) {
            int partialComparsion = myMatchers[i].compareTo(theirMatchers[i]);
            if (partialComparsion < 0) {
                comparsionResult--;
            } else if (partialComparsion > 0) {
                comparsionResult++;
            }
        }

        return comparsionResult;
    }
}
